﻿using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Xml.Linq;
using WebHelper.Helpers;

namespace WebHelper.Handlers
{
    [Serializable]
    public class DownloadHandler2:IHttpHandler
    {
        #region Static Implementation

        public static string DefaultPath
        {
            get
            {
                return "Download.axd";
            }
        }
        public static string FileName
        {
            get
            {
                return "F";
            }
        }
        public static string Url
        {
            get
            {
                return "U";
            }
        }
        public static string Register(bool writeToWebConfig, string filePath)
        {
            string path = DownloadHandler.DefaultPath;
            if (string.IsNullOrEmpty(filePath))
            {
                filePath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
            }
            XDocument document = XDocument.Load(filePath);
            XElement httpHandlers = XmlHelper.CreateIfNotExist(document.Root, "system.web", "httpHandlers");
            var configElement = httpHandlers.Elements("add").FirstOrDefault(e => e.Attribute("type").Value.Contains(typeof(DownloadHandler).FullName));
            if (configElement != null)
            {
                path = configElement.Attribute("path").Value;
                writeToWebConfig = false;
            }
            XElement handlers = XmlHelper.CreateIfNotExist(document.Root, "system.webServer", "handlers");
            configElement = handlers.Elements("add").FirstOrDefault(e => e.Attribute("type") != null && e.Attribute("type").Value.Contains(typeof(DownloadHandler).FullName));
            if (configElement != null)
            {
                path = configElement.Attribute("path").Value;
                writeToWebConfig = false;
            }
            //iis 6
            configElement = httpHandlers.Elements("remove").FirstOrDefault(e => e.Attribute("name") != null && e.Attribute("name").Value.Contains(typeof(DownloadHandler).FullName));
            XElement element = new XElement("add",
                new XAttribute("path", path),
                new XAttribute("type", typeof(DownloadHandler).FullName + "," + typeof(DownloadHandler).Assembly.FullName),
                new XAttribute("verb", "*"),
                new XAttribute("validate", "false"));
            httpHandlers.Add(element);

            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "register in system.web", typeof(DownloadHandler).Name + "  ");

            //iis7
            configElement = handlers.Elements("remove").FirstOrDefault(e => e.Attribute("name") != null && e.Attribute("name").Value.Contains(typeof(DownloadHandler).FullName));
            if (configElement == null)
            {
                if (handlers.Elements("remove").Count() > 0)
                {
                    handlers.Elements("remove").Last().AddAfterSelf(new XElement("remove", new XAttribute("name", typeof(DownloadHandler).FullName)));
                }
                else
                {
                    handlers.AddFirst(new XElement("remove", new XAttribute("name", typeof(DownloadHandler).FullName)));
                }
            }
            element = new XElement("add",
                new XAttribute("name", typeof(DownloadHandler).FullName),
                new XAttribute("path", path),
                new XAttribute("type", typeof(DownloadHandler).FullName + "," + typeof(DownloadHandler).Assembly.FullName),
                new XAttribute("verb", "*"),
                new XAttribute("preCondition", "integratedMode"));
            handlers.Add(element);


            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "register in system.webServer", typeof(DownloadHandler).Name + "  ");


            if (writeToWebConfig)
            {
                document.Save(filePath);

                //trace
                Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "register and saved at" + filePath, typeof(DownloadHandler).Name + "  ");

            }
            return element.ToString();
        }
        public static void UnRegister(string filePath)
        {
            if (string.IsNullOrEmpty(filePath))
            {
                filePath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
            }
            XDocument document = XDocument.Load(filePath);
            XElement configElement = null;
            XElement httpHandlers = XmlHelper.GetElementsOfPath(document.Root, "system.web", "httpHandlers").FirstOrDefault();
            if (httpHandlers != null)
            {
                configElement = httpHandlers.Elements("add").FirstOrDefault(e => e.Attribute("type").Value.Contains(typeof(DownloadHandler).FullName));
                if (configElement != null)
                {
                    configElement.Remove();
                }
            }
            XElement handlers = XmlHelper.GetElementsOfPath(document.Root, "system.webServer", "handlers").FirstOrDefault();
            if (handlers != null)
            {
                configElement = handlers.Elements("remove").FirstOrDefault(e => e.Attribute("name").Value.Contains(typeof(DownloadHandler).FullName));
                if (configElement != null)
                {
                    configElement.Remove();
                }
                configElement = handlers.Elements("add").FirstOrDefault(e => e.Attribute("type").Value.Contains(typeof(DownloadHandler).FullName));
                if (configElement != null)
                {
                    configElement.Remove();
                }
            }
            document.Save(filePath);

            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "unRegister from web.Congig", typeof(DownloadHandler).Name + "  ");

        }

        public static string getLink(Uri uri, string fileName,bool autoDownload)
        {
            string encodedUrl = DownloadHandler.Url +"="+ HexaDecimalHelper.Encode(Encoding.UTF8.GetBytes(uri.AbsoluteUri)) + "&" + DownloadHandler.FileName + "=" + HexaDecimalHelper.Encode(Encoding.UTF8.GetBytes(fileName));

            string path = DownloadHandler.DefaultPath;
            XDocument document = XDocument.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
            XElement httpHandlers = XmlHelper.CreateIfNotExist(document.Root, "system.web", "httpHandlers");
            var configElement = httpHandlers.Elements("add").FirstOrDefault(e => e.Attribute("type").Value.Contains(typeof(DownloadHandler).FullName));
            if (configElement != null)
            {
                path = configElement.Attribute("path").Value;
            }
            XElement handlers = XmlHelper.CreateIfNotExist(document.Root, "system.webServer", "handlers");
            configElement = handlers.Elements("add").FirstOrDefault(e => e.Attribute("type") != null && e.Attribute("type").Value.Contains(typeof(DownloadHandler).FullName));
            if (configElement != null)
            {
                path = configElement.Attribute("path").Value;
            }

            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "Url = " + uri.AbsoluteUri + ", FileName = " + fileName + ", EncodedUrl = " + encodedUrl, typeof(DownloadHandler).Name + "  ");
            if (autoDownload)
            {
                HttpContext.Current.Response.Filter = new DownloadFilter(HttpContext.Current.Response.Filter, UrlHelper.GetCompleteUri("~/" + path + "?" + encodedUrl));
            }
            
            return UrlHelper.GetCompleteUri("~/" + path + "?" + encodedUrl);
        }
        public void ResponseStream(HttpContext context, Stream stream, string fileName, long fileLength)
        {
            context.Response.Clear();
            if (fileLength > 0)
            {
                using (BinaryReader br = new BinaryReader(stream))
                {
                    long startBytes = 0;

                    context.Response.AddHeader("Accept-Ranges", "bytes");
                    context.Response.Buffer = false;
                    int pack = 10240; //10K bytes
                    //int sleep = (int)Math.Floor((double)(1000 * pack / speed)) + 1;
                    if (context.Request.Headers["Range"] != null)
                    {
                        context.Response.StatusCode = 206;
                        string[] range = context.Request.Headers["Range"].Split(new char[] { '=', '-' });
                        startBytes = Convert.ToInt64(range[1]);
                    }
                    context.Response.AddHeader("Content-Length", (fileLength - startBytes).ToString());
                    if (startBytes != 0 && stream.CanSeek)
                    {
                        context.Response.AddHeader("Content-Range", string.Format(" bytes {0}-{1}/{2}", startBytes, fileLength - 1, fileLength));
                    }
                    context.Response.AddHeader("Connection", "Keep-Alive");
                    context.Response.ContentType = "application/octet-stream";
                    context.Response.AddHeader("Content-Disposition", "attachment;filename=" + HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8));
                    if (stream.CanSeek)
                    {
                        br.BaseStream.Seek(startBytes, SeekOrigin.Begin);
                    }
                    int maxCount = (int)Math.Floor((double)((fileLength - startBytes) / pack)) + 1;
                    for (int i = 0; i < maxCount; i++)
                    {
                        if (context.Response.IsClientConnected)
                        {
                            context.Response.BinaryWrite(br.ReadBytes(pack));
                            context.Response.Flush();
                            //Thread.Sleep(sleep);

                            //trace
                            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "resonse flush: no=" + i + ",pack=" + pack, typeof(DownloadHandler).Name + "  ");

                        }
                        else
                        {
                            i = maxCount;

                            //trace
                            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "client not conected", typeof(DownloadHandler).Name + "  ");
                        }
                    }
                }
                context.Response.End();
            }
        }

        #endregion


        public bool IsReusable
        {
            get { return true; }
        }
        //public void ProcessRequest(HttpContext context)
        //{
        //    string urlParam =Encoding.UTF8.GetString(HexaDecimalHelper.Decode(context.Request.QueryString[DownloadHandler.Url]));
        //    string FileNameParam = Encoding.UTF8.GetString(HexaDecimalHelper.Decode(context.Request.QueryString[DownloadHandler.FileName]));
        //    if (!string.IsNullOrEmpty(urlParam) && !string.IsNullOrEmpty(FileNameParam))
        //    {
        //        if (UrlHelper.IsInternalUri(urlParam))
        //        {
        //            string resousrcePath = context.Server.MapPath(new Uri(urlParam).AbsolutePath);
        //            FileInfo fi = new FileInfo(resousrcePath);
        //            context.Response.Headers.Add("Last-Modified", fi.LastWriteTimeUtc.ToString("r"));
        //            context.Response.Headers.Add("ETag", "\"DownLoad" + fi.LastWriteTimeUtc.ToString("yyyyMMddHHmmss") + "\"");
        //            using (FileStream fs = new FileStream(resousrcePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
        //            {
        //                long length = fs.Length;//for trace

        //                this.ResponseStream(context, fs, FileNameParam, fs.Length);
        //                //trace
        //                Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "Download internally(filestram):" + urlParam + ",filename=" + FileNameParam + ",length=" + length, typeof(DownloadHandler).Name + "  ");

        //            }
        //        }
        //        else
        //        {
        //            WebRequest request = WebRequest.Create(urlParam);
        //            WebResponse response = request.GetResponse();
        //            context.Response.Headers.Add("Last-Modified", response.Headers[HttpResponseHeader.LastModified]);
        //            context.Response.Headers.Add("ETag", response.Headers[HttpResponseHeader.ETag]);
        //            using (Stream str = response.GetResponseStream())
        //            {
        //                long length = response.ContentLength;//for trace

        //                this.ResponseStream(context, str, FileNameParam, response.ContentLength);

        //                //trace
        //                Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "Download Externally(responseStream):" + urlParam + ",filename=" + FileNameParam + ",length=" + length, typeof(DownloadHandler).Name + "  ");

        //            }
        //        }
        //    }
        //    else
        //    {
        //        context.Response.StatusCode = 404;

        //        //trace
        //        Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "no data found", typeof(DownloadHandler).Name + "  ");

        //    }
        //}



            #region Constants
    private const string MULTIPART_BOUNDARY= "<q1w2e3r4t5y6u7i8o9p0>";
    private const string MULTIPART_CONTENTTYPE= "multipart/byteranges; boundary=" + MULTIPART_BOUNDARY;
    private const string DEFAULT_CONTENTTYPE = "application/octet-stream";
    private const string HTTP_HEADER_ACCEPT_RANGES= "Accept-Ranges";
    private const string HTTP_HEADER_ACCEPT_RANGES_BYTES = "bytes";
    private const string HTTP_HEADER_ACCEPT_RANGES_NONE = "none";
    private const string HTTP_HEADER_CONTENT_TYPE= "Content-Type";
    private const string HTTP_HEADER_CONTENT_RANGE= "Content-Range";
    private const string HTTP_HEADER_CONTENT_LENGTH= "Content-Length";
    private const string HTTP_HEADER_ENTITY_TAG= "ETag";
    private const string HTTP_HEADER_LAST_MODIFIED= "Last-Modified";
    private const string HTTP_HEADER_RANGE= "Range";
    private const string HTTP_HEADER_IF_RANGE= "If-Range";
    private const string HTTP_HEADER_IF_MATCH= "If-Match";
    private const string HTTP_HEADER_IF_NONE_MATCH= "If-None-Match";
    private const string HTTP_HEADER_IF_MODIFIED_SINCE= "If-Modified-Since";
    private const string HTTP_HEADER_IF_UNMODIFIED_SINCE= "If-Unmodified-Since";
    private const string HTTP_HEADER_UNLESS_MODIFIED_SINCE= "Unless-Modified-Since";
    private const string HTTP_METHOD_GET= "GET";
    private const string HTTP_METHOD_HEAD= "HEAD";
    
    private const int DEBUGGING_SLEEP_TIME = 500;
    #endregion

    #region Constructor
    public DownloadHandler2()
    {
        this.ProcessRequestCheckSteps = 
            new Func<HttpContext, bool>[]
            {
                CheckAuthorizationRules,
                CheckHttpMethod,
                CheckFileRequested,
                CheckRangesRequested,
                CheckIfModifiedSinceHeader,
                CheckIfUnmodifiedSinceHeader,
                CheckIfMatchHeader,
                CheckIfNoneMatchHeader,
                CheckIfRangeHeader
            };
    }
    #endregion 

    #region Properties
    /// <summary>
    /// Indicates if the HTTP request is for multiple ranges.
    /// </summary>
    public bool IsMultipartRequest { get; private set; }

    /// <summary>
    /// Indicates if the HTTP request is for one or more ranges.
    /// </summary>
    public bool IsRangeRequest { get; private set; }

    /// <summary>
    /// The start byte(s) for the requested range(s).
    /// </summary>
    public long[] StartRangeBytes { get; private set; }

    /// <summary>
    /// The end byte(s) for the requested range(s).
    /// </summary>
    public long[] EndRangeBytes { get; private set; }

    /// <summary>
    /// The size of each chunk of data streamed back to the client.
    /// </summary>
    /// <remarks>
    /// When a client makes a range request the requested file's contents are
    /// read in BufferSize chunks, with each chunk flushed to the output stream
    /// until the requested byte range has been read.
    /// </remarks>
    public virtual int BufferSize { get { return 10240; } }

    /// <summary>
    /// Indicates the path to the log file that records HTTP request and response headers.
    /// </summary>
    /// <remarks>
    /// The log is only enabled when the application is executing in Debug mode.
    /// </remarks>
    public virtual string LogFileName { get { return "~/ResumableFileDownloadHandler.log"; } }

    /// <summary>
    /// Indicates whether Range requests are enabled. If false, the HTTP Handler
    /// ignores the Range HTTP Header and returns the entire contents.
    /// </summary>
    public virtual bool EnableRangeRequests { get { return true; } }

    private Func<HttpContext, bool>[] ProcessRequestCheckSteps { get; set; }
    private FileInfo InternalRequestedFileInfo { get; set; }
    private string InternalRequestedFileEntityTag { get; set; }
    private string InternalRequestedFileMimeType { get; set; }
    private NameValueCollection InternalResponseHeaders = new NameValueCollection();
    #endregion

    #region Methods
    /// <summary>
    /// Returns a FileInfo object representing the requested content.
    /// </summary>
    public FileInfo GetRequestedFileInfo(HttpContext context)
    {
        return new FileInfo("");
    }
    
    /// <summary>
    /// Returns the Entity Tag (ETag) for the requested content.
    /// </summary>
    /// <remarks>
    /// The Entity Tag is computed by taking the physical path to the file, concatenating it with the
    /// file's created date and time, and computing the MD5 hash of that string.
    /// 
    /// A derived class MAY override this method to return an Entity Tag
    /// value computed using an alternate approach.
    /// </remarks>
    public virtual string GetRequestedFileEntityTag(HttpContext context)
    {
        FileInfo requestedFile = this.GetRequestedFileInfo(context);
        if (requestedFile == null)
            return string.Empty;

        ASCIIEncoding ascii = new ASCIIEncoding();
        byte[] sourceBytes = ascii.GetBytes(
                                string.Concat(
                                    requestedFile.FullName,
                                    "|",
                                    requestedFile.LastWriteTimeUtc
                                )
                             );

        return Convert.ToBase64String(new MD5CryptoServiceProvider().ComputeHash(sourceBytes));
    }
    
    /// <summary>
    /// Returns the MIME type for the requested content.
    /// </summary>
    /// <remarks>
    /// A dervied class SHOULD override this method and return the MIME type specific
    /// to the requested content. See http://www.iana.org/assignments/media-types/ for
    /// a list of MIME types registered with the Internet Assigned Numbers Authority (IANA).
    /// </remarks>
    public virtual string GetRequestedFileMimeType(HttpContext context)
    {
        return DEFAULT_CONTENTTYPE;
    }

    public void ProcessRequest(HttpContext context)
    {
        this.InternalRequestedFileInfo = this.GetRequestedFileInfo(context);
        this.InternalRequestedFileEntityTag = this.GetRequestedFileEntityTag(context);
        this.InternalRequestedFileMimeType = this.GetRequestedFileMimeType(context);

#if DEBUG
        LogRequestHttpHeaders(context.Server.MapPath(this.LogFileName), context.Request);
#endif

        // Parse the Range header (if it exists), populating the StartRangeBytes and EndRangeBytes arrays
        ParseRequestHeaderRanges(context);

        // Perform each check; exit if any check returns false
        if (!ProcessRequestCheckSteps.Any(i => i(context)))
        {
#if DEBUG
                LogResponseHttpHeaders(context.Server.MapPath(this.LogFileName), context.Response);
#endif
            return;
        }


//        foreach (var check in ProcessRequestCheckSteps)
//            if (check(context) == false)
//            {
//#if DEBUG
//                LogResponseHttpHeaders(context.Server.MapPath(this.LogFileName), context.Response);
//#endif
//                return;
//            }

        // Checks passed, process request!

        if (!this.EnableRangeRequests || !this.IsRangeRequest)
            ReturnEntireEntity(context);
        else
            ReturnPartialEntity(context);
    }

    private void ReturnEntireEntity(HttpContext context)
    {
        HttpRequest Request = context.Request;
        HttpResponse Response = context.Response;

        Response.StatusCode = 200;  // OK
        WriteCommonResponseHeaders(Response, this.InternalRequestedFileInfo.Length, this.InternalRequestedFileMimeType);
        
#if DEBUG
        LogResponseHttpHeaders(context.Server.MapPath(this.LogFileName), Response);
        ReturnChunkedResponse(context);
#else
        if (Request.HttpMethod.Equals(HTTP_METHOD_HEAD) == false)
            Response.TransmitFile(this.InternalRequestedFileInfo.FullName);
#endif
    }

    private void ReturnPartialEntity(HttpContext context)
    {
        HttpRequest Request = context.Request;
        HttpResponse Response = context.Response;

        Response.StatusCode = 206;  // Partial response
        
        // Specify the byte range being returned for non-multipart requests
        if (this.IsMultipartRequest == false)
            AddHeader(Response, HTTP_HEADER_CONTENT_RANGE,
                                    string.Format("bytes {0}-{1}/{2}",
                                                    this.StartRangeBytes[0].ToString(),
                                                    this.EndRangeBytes[0].ToString(),
                                                    this.InternalRequestedFileInfo.Length.ToString()
                                                  )
                                );

        WriteCommonResponseHeaders(Response, 
                                   ComputeContentLength(),
                                   this.IsMultipartRequest ? MULTIPART_CONTENTTYPE : this.InternalRequestedFileMimeType);

#if DEBUG
        LogResponseHttpHeaders(context.Server.MapPath(this.LogFileName), Response);
#endif

        if (Request.HttpMethod.Equals(HTTP_METHOD_HEAD) == false)
            ReturnChunkedResponse(context);
    }

    private void ReturnChunkedResponse(HttpContext context)
    {
        HttpResponse Response = context.Response;

        byte[] buffer = new byte[this.BufferSize];
        using (FileStream fs = this.InternalRequestedFileInfo.OpenRead())
        {
            for (int i = 0; i < this.StartRangeBytes.Length; i++)
            {
                // Position the stream at the starting byte
                fs.Seek(this.StartRangeBytes[i], SeekOrigin.Begin);

                int bytesToReadRemaining = Convert.ToInt32(this.EndRangeBytes[i] - this.StartRangeBytes[i]) + 1;

                // Output multipart boundary, if needed
                if (this.IsMultipartRequest)
                {
                    Response.Output.Write("--" + MULTIPART_BOUNDARY);
                    Response.Output.Write(string.Format("{0}: {1}", HTTP_HEADER_CONTENT_TYPE, this.InternalRequestedFileMimeType));
                    Response.Output.Write(string.Format("{0}: bytes {1}-{2}/{3}",
                                                            HTTP_HEADER_CONTENT_RANGE,
                                                            this.StartRangeBytes[i].ToString(),
                                                            this.EndRangeBytes[i].ToString(),
                                                            this.InternalRequestedFileInfo.Length.ToString()
                                                        )
                                         );
                    Response.Output.WriteLine();
                }

                // Stream out the requested chunks for the current range request
                while (bytesToReadRemaining > 0)
                {
                    if (Response.IsClientConnected)
                    {
                        int chunkSize = fs.Read(buffer, 0, this.BufferSize < bytesToReadRemaining ? this.BufferSize : bytesToReadRemaining);
                        Response.OutputStream.Write(buffer, 0, chunkSize);

                        bytesToReadRemaining -= chunkSize;

                        Response.Flush();

#if DEBUG
                        System.Threading.Thread.Sleep(DEBUGGING_SLEEP_TIME);
#endif
                    }
                    else
                    {
                        // Client disconnected - quit
                        return;
                    }
                }
            }

            fs.Close();
        }
    }

    private int ComputeContentLength()
    {
        int contentLength = 0;

        for (int i = 0; i < this.StartRangeBytes.Length; i++)
        {
            contentLength += Convert.ToInt32(this.EndRangeBytes[i] - this.StartRangeBytes[i]) + 1;

            if (this.IsMultipartRequest)
                contentLength += MULTIPART_BOUNDARY.Length
                                + this.InternalRequestedFileMimeType.Length
                                + this.StartRangeBytes[i].ToString().Length
                                + this.EndRangeBytes[i].ToString().Length
                                + this.InternalRequestedFileInfo.Length.ToString().Length
                                + 49;       // Length needed for multipart header
        }

        if (this.IsMultipartRequest)
            contentLength += MULTIPART_BOUNDARY.Length
                                + 8;    // Length of dash and line break

        return contentLength;
    }

    private void WriteCommonResponseHeaders(HttpResponse Response, long contentLength, string contentType)
    {
        AddHeader(Response, HTTP_HEADER_CONTENT_LENGTH, contentLength.ToString());
        AddHeader(Response, HTTP_HEADER_CONTENT_TYPE, contentType);
        AddHeader(Response, HTTP_HEADER_LAST_MODIFIED, this.InternalRequestedFileInfo.LastWriteTimeUtc.ToString("r"));
        AddHeader(Response, HTTP_HEADER_ENTITY_TAG, string.Concat("\"", this.InternalRequestedFileEntityTag, "\""));

        if (this.EnableRangeRequests)
            AddHeader(Response, HTTP_HEADER_ACCEPT_RANGES, HTTP_HEADER_ACCEPT_RANGES_BYTES);
        else
            AddHeader(Response, HTTP_HEADER_ACCEPT_RANGES, HTTP_HEADER_ACCEPT_RANGES_NONE);
    }

    private string RetrieveHeader(HttpRequest Request, string headerName, string defaultValue)
    {
        return string.IsNullOrEmpty(Request.Headers[headerName]) ? defaultValue : Request.Headers[headerName].Replace("\"", string.Empty);
    }

    protected virtual void ParseRequestHeaderRanges(HttpContext context)
    {
        HttpRequest Request = context.Request;
        HttpResponse Response = context.Response;

        string rangeHeader = RetrieveHeader(Request, HTTP_HEADER_RANGE, string.Empty);

        if (string.IsNullOrEmpty(rangeHeader))
        {
            // No Range HTTP Header supplied; send back entire contents
            this.StartRangeBytes = new long[] { 0 };
            this.EndRangeBytes = new long[] { this.InternalRequestedFileInfo.Length - 1 };
            this.IsRangeRequest = false;
            this.IsMultipartRequest = false;
        }
        else
        {
            // rangeHeader contains the value of the Range HTTP Header and can have values like:
            //      Range: bytes=0-1            * Get bytes 0 and 1, inclusive
            //      Range: bytes=0-500          * Get bytes 0 to 500 (the first 501 bytes), inclusive
            //      Range: bytes=400-1000       * Get bytes 500 to 1000 (501 bytes in total), inclusive
            //      Range: bytes=-200           * Get the last 200 bytes
            //      Range: bytes=500-           * Get all bytes from byte 500 to the end
            //
            // Can also have multiple ranges delimited by commas, as in:
            //      Range: bytes=0-500,600-1000 * Get bytes 0-500 (the first 501 bytes), inclusive plus bytes 600-1000 (401 bytes) inclusive

            // Remove "Ranges" and break up the ranges
            string[] ranges = rangeHeader.Replace("bytes=", string.Empty).Split(",".ToCharArray());

            this.StartRangeBytes = new long[ranges.Length];
            this.EndRangeBytes = new long[ranges.Length];

            this.IsRangeRequest = true;
            this.IsMultipartRequest = (this.StartRangeBytes.Length > 1);

            for (int i = 0; i < ranges.Length; i++)
            {
                const int START = 0, END = 1;

                // Get the START and END values for the current range
                string[] currentRange = ranges[i].Split("-".ToCharArray());

                if (string.IsNullOrEmpty(currentRange[END]))
                    // No end specified
                    this.EndRangeBytes[i] = this.InternalRequestedFileInfo.Length - 1;
                else
                    // An end was specified
                    this.EndRangeBytes[i] = long.Parse(currentRange[END]);

                if (string.IsNullOrEmpty(currentRange[START]))
                {
                    // No beginning specified, get last n bytes of file
                    this.StartRangeBytes[i] = this.InternalRequestedFileInfo.Length - 1 - this.EndRangeBytes[i];
                    this.EndRangeBytes[i] = this.InternalRequestedFileInfo.Length - 1;
                }
                else
                {
                    // A normal begin value
                    this.StartRangeBytes[i] = long.Parse(currentRange[0]);
                }
            }
        }
    }

    /// <summary>
    /// Adds an HTTP Response Header
    /// </summary>
    /// <remarks>
    /// This method is used to store the Response Headers in a private, member variable,
    /// InternalResponseHeaders, so that the Response Headers may be accesed in the
    /// LogResponseHttpHeaders method, if needed. The Response.Headers property can only
    /// be accessed directly when using IIS 7's Integrated Pipeline mode. This workaround
    /// permits logging of Response Headers when using Classic mode or a web server other
    /// than IIS 7.
    /// </remarks>
    protected void AddHeader(HttpResponse Response, string name, string value)
    {
        InternalResponseHeaders.Add(name, value);

        Response.AddHeader(name, value);
    }

    #region Process Request Step Checks
    protected virtual bool CheckAuthorizationRules(HttpContext context)
    {
        // Any authorization checks should be implemented in the derived class
        return true;
    }

    protected virtual bool CheckHttpMethod(HttpContext context)
    {
        HttpRequest Request = context.Request;
        HttpResponse Response = context.Response;

        if (!Request.HttpMethod.Equals(HTTP_METHOD_GET) &&
            !Request.HttpMethod.Equals(HTTP_METHOD_HEAD))
        {
            Response.StatusCode = 501;  // Not Implemented
            return false;
        }

        return true;
    }

    protected virtual bool CheckFileRequested(HttpContext context)
    {
        HttpRequest Request = context.Request;
        HttpResponse Response = context.Response;

        if (this.InternalRequestedFileInfo == null)
        {
            Response.StatusCode = 404;  // Not Found
            return false;
        }

        if (this.InternalRequestedFileInfo.Length > int.MaxValue)
        {
            Response.StatusCode = 413; // Request Entity Too Large
            return false;
        }

        return true;
    }

    protected virtual bool CheckRangesRequested(HttpContext context)
    {
        HttpRequest Request = context.Request;
        HttpResponse Response = context.Response;

        for (int i = 0; i < this.StartRangeBytes.Length; i++)
        {
            if (this.StartRangeBytes[i] > this.InternalRequestedFileInfo.Length - 1 ||
                this.EndRangeBytes[i] > this.InternalRequestedFileInfo.Length - 1)
            {
                context.Response.StatusCode = 400; // Bad Request
                return false;
            }

            if (this.StartRangeBytes[i] < 0 || this.EndRangeBytes[i] < 0)
            {
                context.Response.StatusCode = 400; // Bad Request
                return false;
            }

            if (this.EndRangeBytes[i] < this.StartRangeBytes[i])
            {
                context.Response.StatusCode = 400; // Bad Request
                return false;
            }
        }

        return true;
    }

    protected virtual bool CheckIfModifiedSinceHeader(HttpContext context)
    {
        HttpRequest Request = context.Request;
        HttpResponse Response = context.Response;

        string ifModifiedSinceHeader = RetrieveHeader(Request, HTTP_HEADER_IF_MODIFIED_SINCE, string.Empty);

        if (!string.IsNullOrEmpty(ifModifiedSinceHeader))
        {
            // Determine the date
            DateTime ifModifiedSinceDate;
            DateTime.TryParse(ifModifiedSinceHeader, out ifModifiedSinceDate);

            if (ifModifiedSinceDate == DateTime.MinValue)
                // Could not parse date... do not continue on with check
                return true;

            DateTime requestedFileModifiedDate = this.InternalRequestedFileInfo.LastWriteTimeUtc;
            requestedFileModifiedDate = new DateTime(
                                            requestedFileModifiedDate.Year,
                                            requestedFileModifiedDate.Month,
                                            requestedFileModifiedDate.Day,
                                            requestedFileModifiedDate.Hour,
                                            requestedFileModifiedDate.Minute,
                                            requestedFileModifiedDate.Second
                                        );
            ifModifiedSinceDate = ifModifiedSinceDate.ToUniversalTime();

            if (requestedFileModifiedDate <= ifModifiedSinceDate)
            {
                // File was created before specified date
                Response.StatusCode = 304;  // Not Modified
                return false;
            }
        }

        return true;
    }

    protected virtual bool CheckIfUnmodifiedSinceHeader(HttpContext context)
    {
        HttpRequest Request = context.Request;
        HttpResponse Response = context.Response;

        string ifUnmodifiedSinceHeader = RetrieveHeader(Request, HTTP_HEADER_IF_UNMODIFIED_SINCE, string.Empty);

        if (string.IsNullOrEmpty(ifUnmodifiedSinceHeader))
            // Look for Unless-Modified-Since header
            ifUnmodifiedSinceHeader = RetrieveHeader(Request, HTTP_HEADER_UNLESS_MODIFIED_SINCE, string.Empty);

        if (!string.IsNullOrEmpty(ifUnmodifiedSinceHeader))
        {
            // Determine the date
            DateTime ifUnmodifiedSinceDate;
            DateTime.TryParse(ifUnmodifiedSinceHeader, out ifUnmodifiedSinceDate);

            DateTime requestedFileModifiedDate = this.InternalRequestedFileInfo.LastWriteTimeUtc;
            requestedFileModifiedDate = new DateTime(
                                            requestedFileModifiedDate.Year,
                                            requestedFileModifiedDate.Month,
                                            requestedFileModifiedDate.Day,
                                            requestedFileModifiedDate.Hour,
                                            requestedFileModifiedDate.Minute,
                                            requestedFileModifiedDate.Second
                                        );
            if (ifUnmodifiedSinceDate != DateTime.MinValue)
                ifUnmodifiedSinceDate = ifUnmodifiedSinceDate.ToUniversalTime();

            if (requestedFileModifiedDate > ifUnmodifiedSinceDate)
            {
                // Could not convert value into date or file was created after specified date
                Response.StatusCode = 412;  // Precondition failed
                return false;
            }
        }

        return true;
    }

    protected virtual bool CheckIfMatchHeader(HttpContext context)
    {
        HttpRequest Request = context.Request;
        HttpResponse Response = context.Response;

        string ifMatchHeader = RetrieveHeader(Request, HTTP_HEADER_IF_MATCH, string.Empty);

        if (string.IsNullOrEmpty(ifMatchHeader) || ifMatchHeader == "*")
            return true;        // Match found

        // Look for a matching ETag value in ifMatchHeader
        string[] entityIds = ifMatchHeader.Replace("bytes=", string.Empty).Split(",".ToCharArray());

        foreach (string entityId in entityIds)
        {
            if (this.InternalRequestedFileEntityTag == entityId)
                return true;        // Match found
        }

        // If we reach here, no match found
        Response.StatusCode = 412;  // Precondition failed
        return false;
    }

    protected virtual bool CheckIfNoneMatchHeader(HttpContext context)
    {
        HttpRequest Request = context.Request;
        HttpResponse Response = context.Response;

        string ifNoneMatchHeader = RetrieveHeader(Request, HTTP_HEADER_IF_NONE_MATCH, string.Empty);

        if (string.IsNullOrEmpty(ifNoneMatchHeader))
            return true;

        if (ifNoneMatchHeader == "*")
        {
            // Logically invalid request
            Response.StatusCode = 412;  // Precondition failed
            return false;
        }

        // Look for a matching ETag value in ifNoneMatchHeader
        string[] entityIds = ifNoneMatchHeader.Replace("bytes=", string.Empty).Split(",".ToCharArray());

        foreach (string entityId in entityIds)
        {
            if (this.InternalRequestedFileEntityTag == entityId)
            {
                AddHeader(Response, HTTP_HEADER_ENTITY_TAG, string.Concat("\"", entityId, "\""));
                Response.StatusCode = 304;  // Not modified
                return false;        // Match found
            }
        }

        // No match found
        return true;
    }

    protected virtual bool CheckIfRangeHeader(HttpContext context)
    {
        HttpRequest Request = context.Request;
        HttpResponse Response = context.Response;

        string ifRangeHeader = RetrieveHeader(Request, HTTP_HEADER_IF_RANGE, this.InternalRequestedFileEntityTag);

        if (this.IsRangeRequest && ifRangeHeader != this.InternalRequestedFileEntityTag)
        {
            ReturnEntireEntity(context);
            return false;
        }

        return true;
    }
    #endregion

    #region Logging Methods
    private void LogRequestHttpHeaders(string logFile, HttpRequest Request)
    {
        string output = string.Concat("REQUEST INFORMATION (", Request.HttpMethod, ")", Environment.NewLine);
        foreach (string name in Request.Headers.Keys)
        {
            output += string.Format("{0}: {1}{2}", name, Request.Headers[name], Environment.NewLine);
        }

        output += Environment.NewLine + Environment.NewLine;

        File.AppendAllText(logFile, output);
    }

    private void LogResponseHttpHeaders(string logFile, HttpResponse Response)
    {
        string output = string.Concat("RESPONSE INFORMATION (", Response.StatusCode.ToString(), ")", Environment.NewLine);
        foreach (string name in InternalResponseHeaders.Keys)
        {
            output += string.Format("{0}: {1}{2}", name, InternalResponseHeaders[name], Environment.NewLine);
        }

        output += Environment.NewLine + Environment.NewLine;

        File.AppendAllText(logFile, output);
    }
    #endregion
    #endregion



    }
}
